package ca.swank.hq;

import ca.swank.hardquor.*;
import java.util.ArrayList;
import java.util.LinkedList;


public class hqBoard
{

  public static final int ERROR_NONE = 0;
  public static final int ERROR_PLAYER_BLOCKED = 1;
  public static final int ERROR_INVALID_MOVE = 2;
  public static final int ERROR_NOT_ADJACENT = 3;
  public static final int ERROR_WRONG_PLAYER = 4;
  public static final int ERROR_OCCUPIED_TARGET = 5;
  public static final int ERROR_INSUFFICIENT_WALLS = 6;
  public static final int ERROR_GAME_COMPLETE = 7;

  public static final int PLAYER_WHITE = 0;
  public static final int PLAYER_BLACK = 1;
  public static final int PLAYER_TOTAL = 2;
  public static final int PLAYER_NONE = -1;

  public static final int BOARD_WIDTH = 9;
  public static final int BOARD_HEIGHT = 11;
  public static final int START_WALLS = 10;

  public static final int ADJACENT_WALL = 0;
  public static final int ADJACENT_YES = 1;
  public static final int ADJACENT_OUT = 2;

  public static final int DIR_NONE = -1;
  public static final int DIR_ABOVE = 0;
  public static final int DIR_RIGHT = 1;
  public static final int DIR_BELOW = 2;
  public static final int DIR_LEFT = 3;
  public static final int DIR_TOTAL = 4;

  public static final int TOO_FAR = 2 * BOARD_WIDTH * BOARD_HEIGHT;

  private hqBoardSquare[][] board;
  private int[][] distBoard;

  private int[] playerWalls;
  private hqBoardSquare[] playerLocation;
  private hqBoardSquare[][] playerFinish;

  private int turn;
  private int winner;

  public hqBoard()
  {
	// create board - include out of bounds/adjacency settings for each square
    board = new hqBoardSquare[BOARD_WIDTH][BOARD_HEIGHT];
    for(int x = 0; x < BOARD_WIDTH; x++)
    { for(int y = 0; y < BOARD_HEIGHT; y++)
      { int above = ADJACENT_YES;
        int right = ADJACENT_YES;
        int below = ADJACENT_YES;
        int left = ADJACENT_YES;

        if(y == 0 || y == BOARD_HEIGHT - 1)
        { left = ADJACENT_WALL;
          right = ADJACENT_WALL;
	    }
        if(x == 0) left = ADJACENT_OUT;
        if(x == BOARD_WIDTH - 1) right = ADJACENT_OUT;
        if(y == 0) below = ADJACENT_OUT;
        if(y == BOARD_HEIGHT - 1) above = ADJACENT_OUT;

        board[x][y] = new hqBoardSquare(x,y,above,right,below,left);
      }
    }

	// drop some players on the board
    playerLocation = new hqBoardSquare[PLAYER_TOTAL];
    playerLocation[PLAYER_WHITE] = board[BOARD_WIDTH/2][0];
    playerLocation[PLAYER_BLACK] = board[BOARD_WIDTH/2][BOARD_HEIGHT-1];
    for(int p = 0; p < PLAYER_TOTAL; p++)
    { playerLocation[p].setOccupant(p);
    }

	// give the players some walls
    playerWalls = new int[PLAYER_TOTAL];
    for(int p = 0; p < PLAYER_TOTAL; p++)
    { playerWalls[p] = START_WALLS;
    }

	// give the players a place finish
    playerFinish = new hqBoardSquare[PLAYER_TOTAL][];
    playerFinish[PLAYER_WHITE] = new hqBoardSquare[BOARD_WIDTH];
    for(int i = 0; i < BOARD_WIDTH; i++) playerFinish[PLAYER_WHITE][i] = board[i][BOARD_HEIGHT-1];
    playerFinish[PLAYER_BLACK] = new hqBoardSquare[BOARD_WIDTH];
    for(int i = 0; i < BOARD_WIDTH; i++) playerFinish[PLAYER_BLACK][i] = board[i][0];

	// set the turn, and nobody has won yet
    turn = PLAYER_WHITE;
    winner = PLAYER_NONE;
  }

  public hqBoard(hqBoardSquare[][] board, hqBoardSquare[] playerLocation, int[] playerWalls, int turn, int winner)
  { this.board = board;
    this.playerLocation = playerLocation;
    this.playerWalls = playerWalls;
    this.turn = turn;
    this.winner = winner;

	// give the players a place finish
    playerFinish = new hqBoardSquare[PLAYER_TOTAL][];
    playerFinish[PLAYER_WHITE] = new hqBoardSquare[BOARD_WIDTH];
    for(int i = 0; i < BOARD_WIDTH; i++) playerFinish[PLAYER_WHITE][i] = board[i][BOARD_HEIGHT-1];
    playerFinish[PLAYER_BLACK] = new hqBoardSquare[BOARD_WIDTH];
    for(int i = 0; i < BOARD_WIDTH; i++) playerFinish[PLAYER_BLACK][i] = board[i][0];
  }

  // empty constructor for cloning
  public hqBoard(boolean clone)
  {
  }

  // dupli-dupli-duplicate
  public Object clone()
  {
    hqBoard dolly = new hqBoard(true);

	// clone the board by cloning each square, one at a time
    dolly.board = new hqBoardSquare[board.length][board[0].length];
    for(int x = 0; x < board.length; x++)
    { for(int y = 0; y < board[0].length; y++)
      { dolly.board[x][y] = (hqBoardSquare)board[x][y].clone();
      }
    }

    // clone player locations
    dolly.playerLocation = new hqBoardSquare[PLAYER_TOTAL];
    for(int p = 0; p < PLAYER_TOTAL; p++)
    { dolly.playerLocation[p] = dolly.board[playerLocation[p].x()][playerLocation[p].y()];
    }

    // clone player walls
    dolly.playerWalls = new int[PLAYER_TOTAL];
    for(int p = 0; p < PLAYER_TOTAL; p++)
    { dolly.playerWalls[p] = playerWalls[p];
    }

    // clone player finish locations
    dolly.playerFinish = new hqBoardSquare[PLAYER_TOTAL][];
    for(int p = 0; p < PLAYER_TOTAL; p++)
    {
      dolly.playerFinish[p] = new hqBoardSquare[playerFinish[p].length];
      for(int i = 0; i < playerFinish[p].length; i++)
      { dolly.playerFinish[p][i] = dolly.board[playerFinish[p][i].x()][playerFinish[p][i].y()];
      }
    }

    // same turn, same winner on cloned board
    dolly.turn = turn;
    dolly.winner = winner;

    return dolly;
  }

  /**
   * list all moves for player specified by location
  **/
  public ArrayList getPlayerMoves(hqBoardSquare playerLocation)
  {
    ArrayList playerMoves = new ArrayList();

    int playersCompleted = 0;
    ArrayList adjacentPlayers = new ArrayList();
    adjacentPlayers.add(playerLocation);

    while(playersCompleted < adjacentPlayers.size())
    {
	  hqBoardSquare square = (hqBoardSquare)adjacentPlayers.get(playersCompleted);

      // iterate through all directions, looking for possible moves.
      for(int d = 0; d < DIR_TOTAL; d++)
      {
    	if(square.getAdjacent(d) == ADJACENT_YES)
	    { hqBoardSquare adjacentSquare = getSquareInDirection(d, square.x(), square.y());
    	  if(adjacentSquare.occupant() != PLAYER_NONE && !adjacentPlayers.contains(adjacentSquare)) adjacentPlayers.add(adjacentSquare);
	      else if(!playerMoves.contains(adjacentSquare) && !adjacentPlayers.contains(adjacentSquare)) playerMoves.add(adjacentSquare);
	    }
	  }
      playersCompleted++;
	}

    /* DISPLAY ALL POSSIBLE MOVES:
    System.out.print("moves: ");
	for(int i = 0; i < playerMoves.size(); i++)
	{ System.out.print((hqBoardSquare)playerMoves.get(i));
	}
	System.out.print("\n");
    System.out.print("\n"); */

	return playerMoves;
  }

  // Implicitly get a square from the board
  private hqBoardSquare getSquareInDirection(int direction, int x, int y)
  { if(direction == DIR_ABOVE) return board[x][y+1];
    else if(direction == DIR_RIGHT) return board[x+1][y];
    else if(direction == DIR_BELOW) return board[x][y-1];
    else if(direction == DIR_LEFT) return board[x-1][y];
    else return null; // crash & burn
  }

  // Implicitly get a value from the distance board
  private int getDistBoardInDirection(int direction, int x, int y)
  { if(direction == DIR_ABOVE) return distBoard[x][y+1];
    else if(direction == DIR_RIGHT) return distBoard[x+1][y];
    else if(direction == DIR_BELOW) return distBoard[x][y-1];
    else if(direction == DIR_LEFT) return distBoard[x-1][y];
    else return -1; // crash & burn
  }

  // Implicitly set a value on the distance board
  private void setDistBoardInDirection(int direction, int x, int y, int newValue)
  { if(direction == DIR_ABOVE) distBoard[x][y+1] = newValue;
    else if(direction == DIR_RIGHT) distBoard[x+1][y] = newValue;
    else if(direction == DIR_BELOW) distBoard[x][y-1] = newValue;
    else if(direction == DIR_LEFT) distBoard[x-1][y] = newValue;
  }

  /**
   * draw a map of the distance 'home' or to another arbitrary set of squares
  **/
  public int[][] getDistanceBoard(hqBoardSquare[] zeroDistance)
  {
    distBoard = new int[BOARD_WIDTH][BOARD_HEIGHT];
    for(int x = 0; x < BOARD_WIDTH; x++)
    { for(int y = 0; y < BOARD_HEIGHT; y++)
      { distBoard[x][y] = TOO_FAR;
      }
    }

    LinkedList bfsQueue = new LinkedList();

    for(int i = 0; i < zeroDistance.length; i++)
    { hqBoardSquare square = zeroDistance[i];
      distBoard[square.x()][square.y()] = 0;
      bfsQueue.addLast(square);
    }

    while(!bfsQueue.isEmpty())
    {
	  hqBoardSquare square = (hqBoardSquare)bfsQueue.removeFirst();

	  int x = square.x();
	  int y = square.y();

      for(int d = 0; d < DIR_TOTAL; d++)
      { if(board[x][y].getAdjacent(d) == ADJACENT_YES &&
           getDistBoardInDirection(d, x, y) == TOO_FAR)
        { setDistBoardInDirection(d, x, y, distBoard[x][y] + 1);
          bfsQueue.addLast(getSquareInDirection(d, x, y));
	    }
	  }
	}

    // DISPLAY DISTANCE BOARD
	/* System.out.print("    | ");
	for(int x = 0; x < distBoard.length; x++)
	{ System.out.print(formatNum(x) + " ");
    } System.out.print("\n");
    System.out.print("----+-");
	for(int x = 0; x < distBoard.length; x++)
	{ System.out.print("----");
    } System.out.print("\n");
	for(int y = distBoard[0].length - 1; y >= 0; y--)
	{ System.out.print(formatNum(y) + " | ");
	  for(int x = 0; x < distBoard.length; x++)
	  { System.out.print(formatNum(distBoard[x][y]) + " ");
      }
      System.out.print("\n");
    }
    System.out.print("\n"); */

    return distBoard;

  }

  /**
   * for displaying distance charts, possibly other console shite
  **/
  private String formatNum(int num)
  { if(num >= 100) return "" + num;
    else if(num >= 10) return " " + num;
    else return "  " + num;
  }

  /**
   * like calling your lawyer for a certain move. returns the error code
   * of the move. legal moves return ERROR_NONE
  **/
  public int isLegalMove(hqMove m)
  {
    if(winner != PLAYER_NONE) return ERROR_GAME_COMPLETE;

	else if(m.type() == hqMove.WALL)
	{

      if(playerWalls[turn] <= 0)
      { return ERROR_INSUFFICIENT_WALLS;

      } else
      {
        // horizontal wall - check to see if it is proper
		if(m.a1().x() == m.b1().x())
		{ if(m.a2().x() != m.b2().x()) return ERROR_INVALID_MOVE;
		  if(m.a1().y() != m.a2().y()) return ERROR_INVALID_MOVE;
		  if(m.b1().y() != m.b2().y()) return ERROR_INVALID_MOVE;
		  if(Math.abs(m.a1().x() - m.a2().x()) != 1) return ERROR_INVALID_MOVE;
		  if(Math.abs(m.b1().x() - m.b2().x()) != 1) return ERROR_INVALID_MOVE;
		// vertical wall - check to see if it is proper
	    } else if(m.a1().y() == m.b1().y())
	    { if(m.a2().y() != m.b2().y()) return ERROR_INVALID_MOVE;
	      if(m.a1().x() != m.a2().x()) return ERROR_INVALID_MOVE;
	      if(m.b1().x() != m.b2().x()) return ERROR_INVALID_MOVE;
		  if(Math.abs(m.a1().y() - m.a2().y()) != 1) return ERROR_INVALID_MOVE;
		  if(Math.abs(m.b1().y() - m.b2().y()) != 1) return ERROR_INVALID_MOVE;
	    // neither horizontal nor vertical: must not be proper
	    } else return ERROR_INVALID_MOVE;

        // check to make sure walls aren't atop one another
	    int directionA = m.getDirectionA();
	    int directionB = m.getDirectionB();

        if(directionA == DIR_NONE || directionB == DIR_NONE) return ERROR_INVALID_MOVE;
        if(board[m.a1().x()][m.a1().y()].getAdjacent(directionA) != ADJACENT_YES) return ERROR_INVALID_MOVE;
        if(board[m.b1().x()][m.b1().y()].getAdjacent(directionB) != ADJACENT_YES) return ERROR_INVALID_MOVE;
        if(board[m.a2().x()][m.a2().y()].getAdjacent(directionA) != ADJACENT_YES) return ERROR_INVALID_MOVE;
        if(board[m.b2().x()][m.b2().y()].getAdjacent(directionB) != ADJACENT_YES) return ERROR_INVALID_MOVE;

		hqBoard dolly = (hqBoard)clone();
        dolly.move(m);

        for(int p = 0; p < PLAYER_TOTAL; p++)
        { int[][] result = dolly.getDistanceBoard(dolly.playerFinish[p]);
          if(result[dolly.playerLocation[p].x()][dolly.playerLocation[p].y()] >= TOO_FAR)
          { return ERROR_PLAYER_BLOCKED;
          }
	    }
      }

      return ERROR_NONE;

	} else if(m.type() == hqMove.WALK)
	{

	  if(board[m.source().x()][m.source().y()].occupant() != turn) return ERROR_WRONG_PLAYER;

	  else if(board[m.target().x()][m.target().y()].occupant() != PLAYER_NONE) return ERROR_OCCUPIED_TARGET;

	  ArrayList playerMoves = getPlayerMoves(board[m.source().x()][m.source().y()]);

	  boolean moveFound = false;
	  for(int i = 0; i < playerMoves.size(); i++)
	  { hqBoardSquare target = (hqBoardSquare)playerMoves.get(i);
		if(target.x() == m.target().x() && target.y() == m.target().y()) moveFound = true;
      }

      if(moveFound) return ERROR_NONE;
      else return ERROR_NOT_ADJACENT;
    } else
    {
	  return ERROR_INVALID_MOVE;
    }
  }

  /**
   * makes a move. this does not check to see if the move is legal.
  **/
  public void move(hqMove m)
  {
	if(m.type() == hqMove.WALK)
	{ hqBoardSquare source = board[m.source().x()][m.source().y()];
	  hqBoardSquare target = board[m.target().x()][m.target().y()];

      playerLocation[source.occupant()] = target;

      target.setOccupant(source.occupant());
      source.setOccupant(PLAYER_NONE);

      for(int p = 0; p < PLAYER_TOTAL; p++)
      { for(int i = 0; i < playerFinish[p].length; i++)
        { if(playerLocation[p] == playerFinish[p][i]) winner = p;
	    }
      }

	} else if(m.type() == hqMove.WALL)
	{
	  int directionA = m.getDirectionA();
	  int directionB = m.getDirectionB();

      board[m.a1().x()][m.a1().y()].setAdjacent(directionA, ADJACENT_WALL);
      board[m.b1().x()][m.b1().y()].setAdjacent(directionB, ADJACENT_WALL);
      board[m.a2().x()][m.a2().y()].setAdjacent(directionA, ADJACENT_WALL);
      board[m.b2().x()][m.b2().y()].setAdjacent(directionB, ADJACENT_WALL);

      playerWalls[turn]--;
	}

    // System.out.println(m);

	if(turn == PLAYER_WHITE) turn = PLAYER_BLACK;
    else if(turn == PLAYER_BLACK) turn = PLAYER_WHITE;
  }

  /**
   * un-makes a move. useful for undo capability, and search trees
  **/
  public void unMove(hqMove m)
  {
	if(m.type() == hqMove.WALK)
	{ hqBoardSquare source = board[m.source().x()][m.source().y()];
	  hqBoardSquare target = board[m.target().x()][m.target().y()];

      playerLocation[target.occupant()] = source;

      source.setOccupant(target.occupant());
      target.setOccupant(PLAYER_NONE);

      winner = PLAYER_NONE;

	} else if(m.type() == hqMove.WALL)
	{
	  int directionA = 0;
	  int directionB = 0;

	  if(m.a1().x() - m.b1().x() == 1) // b left of a
	  { directionA = DIR_LEFT;
	    directionB = DIR_RIGHT;

	  } else if(m.a1().x() - m.b1().x() == -1) // a left of b
	  { directionA = DIR_RIGHT;
	    directionB = DIR_LEFT;

	  } else if(m.a1().y() - m.b1().y() == 1) // a above b
	  { directionA = DIR_BELOW;
	    directionB = DIR_ABOVE;

	  } else if(m.a1().y() - m.b1().y() == -1) // b above a
	  { directionA = DIR_ABOVE;
	    directionB = DIR_BELOW;
	  }

      board[m.a1().x()][m.a1().y()].setAdjacent(directionA, ADJACENT_YES);
      board[m.b1().x()][m.b1().y()].setAdjacent(directionB, ADJACENT_YES);
      board[m.a2().x()][m.a2().y()].setAdjacent(directionA, ADJACENT_YES);
      board[m.b2().x()][m.b2().y()].setAdjacent(directionB, ADJACENT_YES);

      playerWalls[turn]++;

	}

    if(turn == PLAYER_BLACK) turn = PLAYER_WHITE;
	else if(turn == PLAYER_WHITE) turn = PLAYER_BLACK;

  }

  /**
   * games winner
  **/
  public int winner()
  { return winner;
  }

  /**
   * how many walls
  **/
  public int walls(int player)
  { return playerWalls[player];
  }

  /**
   * who's turn
  **/
  public int turn()
  { return turn;
  }

  /**
   * get a square
  **/
  public hqBoardSquare getSquare(int x, int y)
  { return board[x][y];
  }

  /**
   * who lives here
  **/
  public int getOccupant(int x, int y)
  { return board[x][y].occupant();
  }

  /**
   * where am i
  **/
  public hqBoardSquare getPlayerLoc(int player)
  { return playerLocation[player];
  }

  /**
   * where is home
  **/
  public hqBoardSquare[] getPlayerFinish(int player)
  { return playerFinish[player];
  }


  /**
  * BEGIN EXPERIMENTAL AREA *************************************************888
  */

  public String getAddress()
  {

    nextDirection = DIR_BELOW;
    nextX = -1;
    nextY = 1;

    hqBoardSquare s = next();


    String r = "";

	int posType = ADJACENT_YES;
	int posCount = 0;

	int curPos = -1;

    while(s != null)
    {
      curPos = s.getAdjacent(nextDirection);

      System.out.println(nextX + " " + nextY + " CURPOS " + curPos + " POSTYPE " + posType + " POSCOUNT " + posCount);

      if(curPos == posType)
      { // both the same
   	    if(posType == ADJACENT_YES)
		{ posCount++;
		  if(posCount == 3)
		  { r = r + "11";
		    posCount = 0;
		  }
		} else if(posType == ADJACENT_WALL)
		{ posCount++;
		  if(posCount == 2)
		  { r = r + "10";
		    posCount = 0;
		  }
	    }
	  } else
	  { if(posType == ADJACENT_YES)
		{ for(int i = 0; i < posCount; i++) r = r + "0";
		  posType = curPos;
		  posCount = 1;
		} else if(posType == ADJACENT_WALL)
		{ posType = curPos;
		  posCount = 1;
	    }
	  }

	  s = next();
    }

    if(posType == ADJACENT_YES)
	{ for(int i = 0; i < posCount; i++) r = r + "0";
	}
    System.out.println("ALL  >" + r + "<" + r.length());

    return r;

  }

  int nextDirection = DIR_BELOW;
  int nextX = 0;
  int nextY = 1;

  public hqBoardSquare next()
  {
	if(nextDirection == DIR_BELOW)
	{
	  nextX++;

      if(nextX == BOARD_WIDTH)
	  { nextX = 0;
	    nextY++;
	  }
	  if(nextY == BOARD_HEIGHT)
	  { nextDirection = DIR_LEFT;
	    nextX = 1;
	    nextY = 1;
      }
    } else if(nextDirection == DIR_LEFT)
    {
 	  nextY++;

 	  if(nextY == BOARD_HEIGHT - 1)
 	  { nextX++;
 	    nextY = 1;
	  }
 	  if(nextX == BOARD_WIDTH)
      { return null;
      }
    }
	return getSquare(nextX,nextY);
  }

  public void decodeAddress(String encode)
  {
    nextDirection = DIR_BELOW;
    nextX = -1;
    nextY = 1;

    hqBoardSquare s = next();

    String matchString = "";

    for(int i = 0; i < encode.length(); i++)
    {
	  matchString = matchString + encode.charAt(i);

	  if(matchString.equals("11"))
	  { for(int j = 0; j < 3; j++)
	    { s.setAdjacent(nextDirection, ADJACENT_YES);
	      s = next();
	    }
	    matchString = "";
      } else if(matchString.equals("10"))
      { for(int j = 0; j < 2; j++)
        { s.setAdjacent(nextDirection, ADJACENT_WALL);
          System.out.println("BUILDING WALL : " + s + " DIR: " + nextDirection);
          s = next();
	    }
	    matchString = "";
      } else if(matchString.equals("0"))
      { s.setAdjacent(nextDirection, ADJACENT_YES);
        s = next();
	    matchString = "";
      }

    }

  }



  /**
  * END EXPERIMENTAL AREA *************************************************888
  */

}